package genetic;


import genetic.q2.RobyPhenotype;
import genetic.q3.SRFileReader;
import genetic.q3.SymbolicRegressionGenotype;
import genetic.q3.SymbolicRegressionPhenotype;
import genetic.q3.functions.Computable;
import genetic.q4.TicTacToeGenotype;
import genetic.q4.TicTacToePhenotype;

import java.io.File;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;

public class GeneticProgramming {

	List<Genotype> population;
	int populationSize;
	Phenotype prototype;
	double bestFitness = 0;
	int bestGen = 0;
	Genotype best = null;
	double Pr;
	double Pc;
	double Pm;
	private Genotype genBest;
	private List<Genotype> allGenerationsBest;

	public GeneticProgramming(Phenotype prototype, double Pr, double Pc, double Pm){
		//normalize probabilities
		this.Pr = Pr/(Pr+Pc+Pm);
		this.Pc = Pc/(Pr+Pc+Pm);
		this.Pm = Pm/(Pr+Pc+Pm);
		this.population = new LinkedList<Genotype>();
		this.allGenerationsBest = new LinkedList<Genotype>();
		this.prototype = prototype;
	}

	public void init(int populationSize){
		this.populationSize = (populationSize - populationSize % 2);
		population.clear();
		population = prototype.getInitialPopulation(this.populationSize);
	}

	public Genotype runAlgorithm(int numGenerations, String resultFile, String runFile){
		PrintWriter out = openFile(resultFile);
		PrintWriter out2 = openFile(runFile);
		double[] initStats;
		bestFitness = Double.MIN_VALUE;
		prototype.updateFitness(population);
		initStats = getStatistics(0);
		out.println(0 + "\t" + initStats[0] + "\t" + initStats[1]);

		for (int generation=1; generation<=numGenerations; generation++){
			List<Genotype> newPopulation = new LinkedList<Genotype>();

			Genotype maxpop = population.get(0);
			double maxfit = prototype.fitness(maxpop);
			for (int i=1; i<populationSize; i++){
				double fitness = prototype.fitness(population.get(i));
				if (fitness > maxfit){
					maxpop = population.get(i);
					maxfit = fitness;
				}
			}
			newPopulation.add(maxpop.clone());

			while(newPopulation.size() < populationSize){
				double rand = Math.random();
				Genotype[] selected = prototype.selectParents(population, populationSize);
				if (rand < Pr){
					//reproduction
					newPopulation.add(selected[0].clone());
				} else if (rand <Pr+Pc){
					//crossover
					Genotype[] kids = prototype.crossover(selected[0], selected[1]);
					newPopulation.add(kids[0]);
					newPopulation.add(kids[1]);
				} else {//if (rand<Pr+Pc+Pm){
					//mutation
					newPopulation.add(prototype.mutate(selected[0]));
				}
			}

			population = newPopulation;

			prototype.updateFitness(population);

			double[] stats;
			stats = getStatistics(generation);
			allGenerationsBest.add(genBest);

			if (generation % 5 == 0 || generation < 10)
				System.out.println("Generation "+generation+": Best - " + stats[0] + ", Average - " + stats[1] +  ", Deepest - " + stats[2] + " , Best this gen:" + "\t" + genBest);
			out2.println("Generation "+generation+": Best - " + stats[0] + ", Average - " + stats[1] + ", Deepest - " + stats[2] + " , Best this gen:" + "\t" + genBest);
			out.println(generation + "\t" + stats[0] + "\t" + stats[1]);
		}

		System.out.println("Best genotype on final generation, found on generation "+ bestGen+", fitness - "+bestFitness+ ": " + best);
		out.print("Best genotype on final generation, found on generation "+ bestGen+", fitness - "+bestFitness+ ": " + best);
		out2.print("Best genotype on final generation, found on generation "+ bestGen+", fitness - "+bestFitness+ ": " + best);

		//		if (best instanceof SymbolicRegressionGenotype){
		//
		//			PrintWriter out3 = openFile("points"+runFile);
		//			((SymbolicRegressionGenotype) best).printPoints(((SymbolicRegressionPhenotype)prototype).points, out3);
		//			out3.close();
		//		}

		if (best instanceof TicTacToeGenotype){
			System.out.println(((TicTacToeGenotype)best).minimize());
			out.println(((TicTacToeGenotype)best).minimize());
			out2.println(((TicTacToeGenotype)best).minimize());
		}
		out.close();
		out2.close();

		prototype.updateFitness(allGenerationsBest);
		best = allGenerationsBest.get(0);
		bestFitness = prototype.fitness(best);
		for (Genotype g: allGenerationsBest){
			double tmpFitness = prototype.fitness(g);
			if (tmpFitness>bestFitness){
				best = g;
				bestFitness = tmpFitness;
			}
		}

		System.out.println("Best genotype after comparing best ones from all generations has fitness: " + bestFitness + " - " + best);
		if (best instanceof TicTacToeGenotype)
			System.out.println("Best genotype after comparing best ones from all generations has fitness: " + bestFitness + " - " + ((TicTacToeGenotype)best).minimize());
		return best;
	}

	private double[] getStatistics(int gen){
		double maxFitness = Double.MIN_VALUE;
		double averageFitness = 0;
		double maxDepth = 0;
		genBest = null;
		for (Genotype g: population){
			double fitness = prototype.fitness(g);
			double depth = g.getDepth();
			if(maxFitness < fitness){
				maxFitness = fitness;
				genBest = g;
			}
			if(maxDepth < depth){
				maxDepth = depth;
			}
			averageFitness = averageFitness + fitness;
		}
		averageFitness = averageFitness/population.size();

		if (bestFitness < maxFitness) {
			bestFitness = maxFitness;
			best = genBest;
			bestGen = gen;
		}
		return new double[]{ maxFitness , averageFitness, maxDepth};
	}

	//	private double[] getMinStatistics(int gen){
	//		double maxFitness = Double.MAX_VALUE;
	//		double averageFitness = 0;
	//		double maxDepth = 0;
	//		genBest = null;
	//		for (Genotype g: population){
	//			double fitness = prototype.fitness(g);
	//			double depth = g.getDepth();
	//			if(maxFitness > fitness){
	//				maxFitness = fitness;
	//				genBest = g;
	//			}
	//			if(maxDepth < depth){
	//				maxDepth = depth;
	//			}
	//			averageFitness = averageFitness + fitness;
	//		}
	//		averageFitness = averageFitness/population.size();
	//
	//		if (bestFitness > maxFitness) {
	//			bestFitness = maxFitness;
	//			best = genBest;
	//			bestGen = gen;
	//		}
	//		return new double[]{ maxFitness , averageFitness, maxDepth};
	//	}

	public PrintWriter openFile(String filename){
		try{
			File f = new File(filename);
			f.delete();
			f.createNewFile();
			PrintWriter out = new PrintWriter(f);
			return out;
		}catch (Exception e) {
			return null;
		}
	}

	//	public static void main(final String[] args){
	//		Timer t = new Timer();
	//		t.schedule(new TimerTask(){
	//			@Override
	//			public void run() {
	//				main2(args);
	//			}
	//			
	//		}, 1000*60*25); //schedule in 25 minutes
	//	}

	public static void main(String[] args) {
		int run = 3;
		if (run == 1){
		} else if (run == 2){
			Phenotype prototype = new RobyPhenotype();
			GeneticProgramming gp = new GeneticProgramming(prototype, 0.1,0.80,0.1);
			//	gp.init(100);
			//	gp.runAlgorithm(150, "Roby.txt");
			gp.init(100);
			gp.runAlgorithm(150, "Roby2.txt", "run.txt");
			gp.init(100);
			gp.runAlgorithm(150, "Roby3.txt", "run.txt");
		} else if (run == 3){
			int type = 0;
			for (int trynum=4; trynum<=20; trynum++){
				//				for (int k=1; k<2 ; k++){
				//					for (int i=1; i<=3; i++){
				//			int trynum = 3; {
				int k = 1; {
					int i = 1; {
						double[][] points = null;
						if (i==1){
							points = new SRFileReader().readFile("f1.txt"); type = 1*k;
						} else if (i==2){
							points = new SRFileReader().readFile("f2.txt"); type = 2*k;
						} else if (i==3){
							points = new SRFileReader().readFile("f3.txt"); type = 3*k;
						}
						Phenotype prototype = new SymbolicRegressionPhenotype(points, type);
						GeneticProgramming gp = new GeneticProgramming(prototype, 0.4,0.5,0.1);
						gp.init(100);
						Genotype bg = gp.runAlgorithm(1500, trynum + "-"+k+"Symbol"+i+".txt", trynum + "-"+k+"run"+i+".txt");
						SymbolicRegressionGenotype srg = (SymbolicRegressionGenotype)bg;
						PrintWriter out = gp.openFile(trynum + "-"+k+"newf"+i+".txt");
						for(int j =0 ; j< points.length; j++){
							out.println(points[j][0]+"\t"+((Computable)srg.getTree()).compute(points[j][0]));
						}
						out.close();
					}
				}
			}
		} else if (run == 4){
			Phenotype prototype = new TicTacToePhenotype();
			GeneticProgramming gp = new GeneticProgramming(prototype, 0.1,0.80,0.1);
			gp.init(100);
			gp.runAlgorithm(700, "tictactoe.txt", "run.txt");
		}
	}
}